iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
Modern Web

從零開始打造炫砲個人部落格,使用 Next.js、ContentLayer、i18next 等現代技術系列 第 27

在 kbar Command Palette 實作文章搜尋 - Modern Next.js Blog 系列 #27

  • 分享至 

  • xImage
  •  

這是「Modern Blog 30 天」系列第 27 篇文章。

上一篇加完了 Command Palette 指令面板,這篇我們來繼續擴充它,讓 Command Palette 能搜尋所有文章並跳轉到特定文章內頁!

最終效果如下:

Command palette search post light

Command palette search post dark

這篇修改的程式碼如下:

https://github.com/Kamigami55/nextjs-tailwind-contentlayer-blog-starter/compare/day26-command-palette...day27-search-post

我的個人網站裡也有此系列的好讀版,程式碼更易讀、也支援深色模式和側邊目錄,歡迎前往閱讀!


實作 kbar Command Palette 文章搜尋功能

理想上要擴充 kbar 的選項,只需要修改 CommandPalette.tsx 裡 actions array 就好,import Contentlayer 提供的 allPosts 文章列表放進去應該就行了。

但這邊遇到各種技術限制,所以要改用比較複雜的方式實現。如果你有找到更簡單的方法,歡迎跟我說!

遇到的技術限制如下,跟 Next.js、Contentlayer、kbar 有關:

  • Contentlayer 沒辦法直接在 CommandPalette.tsx 裡 import allPosts,只能在 Next.js pages 的 getStaticProps 裡 import
  • Next.js 目前還不支援在 _app.tsx 裡寫 getStaticProps 給全站用,因此每個頁面的 getStaticProps 都要修改
  • 在各頁面動態加入 Command Palette 選項,需要使用 kbar 提供的 useRegisterActions

所以最終我們的實作方向是:

  1. 新增 getCommandPalettePosts 並放在所有頁面的 getStaticProps 裡,取得所有文章
  2. 新增 useCommandPalettePostActions 並放在所有頁面的 component 內,利用 useRegisterActions 動態加入所有文章到 Command Palette 選項
  3. 修改 CommandPalette component,加入一個「搜尋文章」的 action,集結所有 2. 的文章選項,比較好用

讓我們開始實作吧!

新增 src/components/CommandPalette/getCommandPalettePosts.ts

import { allPostsNewToOld } from '@/lib/contentLayerAdapter';

export type PostForCommandPalette = {
  slug: string;
  title: string;
  path: string;
};

export const getCommandPalettePosts = (): PostForCommandPalette[] => {
  const commandPalettePosts = allPostsNewToOld.map((post) => ({
    slug: post.slug,
    title: post.title,
    path: post.path,
  }));
  return commandPalettePosts;
};

新增 src/components/CommandPalette/useCommandPalettePostActions.tsx

import { useRegisterActions } from 'kbar';
import { useRouter } from 'next/router';

import { PostForCommandPalette } from './getCommandPalettePosts';

export const useCommandPalettePostActions = (
  posts: PostForCommandPalette[]
): void => {
  const router = useRouter();

  useRegisterActions(
    posts.map((post) => ({
      id: post.slug,
      name: post.title,
      perform: () => router.push(post.path),
      section: '搜尋文章',
      parent: 'search-posts',
    })),
    []
  );
};

接著修改每個 Next.js 頁面,加入 getCommandPalettePostsuseCommandPalettePostActions 邏輯。

修改 src/pages/index.tsx

// ...

import {
  getCommandPalettePosts,
  PostForCommandPalette,
} from '@/components/CommandPalette/getCommandPalettePosts';
import { useCommandPalettePostActions } from '@/components/CommandPalette/useCommandPalettePostActions';

// ...

type Props = {
  posts: PostForIndexPage[];
  commandPalettePosts: PostForCommandPalette[];
};

export const getStaticProps: GetStaticProps<Props> = () => {
  const commandPalettePosts = getCommandPalettePosts();

  // ...

  return { props: { posts, commandPalettePosts } };
};

const Home: NextPage<Props> = ({ posts, commandPalettePosts }) => {
  useCommandPalettePostActions(commandPalettePosts);

  // ...
};

// ...

修改 src/pages/posts/[slug].tsx

// ...

import {
  getCommandPalettePosts,
  PostForCommandPalette,
} from '@/components/CommandPalette/getCommandPalettePosts';
import { useCommandPalettePostActions } from '@/components/CommandPalette/useCommandPalettePostActions';

// ...

type Props = {
  post: PostForPostPage;
  prevPost: RelatedPostForPostLayout;
  nextPost: RelatedPostForPostLayout;
  commandPalettePosts: PostForCommandPalette[];
};

// ...

export const getStaticProps: GetStaticProps<Props> = ({ params }) => {
  const commandPalettePosts = getCommandPalettePosts();

  // ...

  return {
    props: {
      post,
      prevPost,
      nextPost,
      commandPalettePosts,
    },
  };
};

const PostPage: NextPage<Props> = ({
  post,
  prevPost,
  nextPost,
  commandPalettePosts,
}) => {
  useCommandPalettePostActions(commandPalettePosts);

  // ...
};

// ...

最後修改 src/components/CommandPalette/CommandPalette.tsx,在 actions array 加入 Search section:

import {
  MagnifyingGlassIcon,
  // ...
} from '@heroicons/react/24/outline';
// ...

export default function CommandPalette({ children }: Props) {
  // ...

  const actions = [
    // Page section
    // ...

    // 加入這個 section
    // Search section
    // - Search posts
    {
      id: 'search-posts',
      name: '文章',
      keywords:
        'search find posts writing words blog articles thoughts 搜尋 尋找 文章 寫作 部落格',
      icon: <MagnifyingGlassIcon className="h-6 w-6" />,
      section: '搜尋',
    },

    // Operation section
    // - Theme toggle
    // ...
  ];

  return (
    <KBarProvider actions={actions}>
      <CommandBar />
      {children}
    </KBarProvider>
  );
}

// ...

成果

這樣就完成了!使用 pnpm dev,進去網站裡按下 Ctrl + K (Windows) 或 Cmd + K (Mac),或是點右上角的 Command icon,開啟 Command Palette 後,就會看到多出「搜尋文章」的操作可以執行了!

最終效果如下:

Command palette search post light

Command palette search post dark

這篇修改的程式碼如下:

https://github.com/Kamigami55/nextjs-tailwind-contentlayer-blog-starter/compare/day26-command-palette...day27-search-post

References

下一篇

恭喜你成功在 Command Palette 指令面板加入文章搜尋功能了!

下一篇我們終於要來完成這個系列最後一塊重點功能:「i18next 多語系支援」了!


上一篇
使用 kbar 加入 Command Palette 指令面板 - Modern Next.js Blog 系列 #26
下一篇
使用 next-i18next 實作中英文多語系 - Modern Next.js Blog 系列 #28
系列文
從零開始打造炫砲個人部落格,使用 Next.js、ContentLayer、i18next 等現代技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言